﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Net.Sockets;
using System.Net;
using System.Security;

//    Copyright (C) 2011 Officebroker.com LTD.
//    Neil Cadman, Alex Welding, Andrew Coombs, Jan Rygielski
//    Sir Robert Peel Mill, Mill Lane, Staffordshire, Fazely, B78 3QD, England
//
//    This file is part of AMIProxy Project.
//
//    AMIProxy Project is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    AMIProxy Project is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License (GNU AGPL)
//    along with AMIProxy Project.  If not, see <http://www.gnu.org/licenses/>.

namespace AMIUtilities
{
    // Private Delegates
    delegate void readSocketDelegate(Socket socket);

    // Public Classes
    public class ManagedSocket
    {
        // Private Variables
        private Socket _socket = null;
        private Boolean _reading = false;
        private Boolean _stopReading = false;
        private Boolean _active = false;
        private String _key;
        private dataReceivedDelegate _dataReceived;
        private List<String> _sendMessages = new List<String>();
        private List<Byte[]> _sendSocketEvent = new List<Byte[]>();
        private const Int32 _sleepTimer = 10;
        private sendDataDelegate _sending;
        private AsyncCallback _sendingCallback;
        private errorDelegate _error;
        private socketClosedDelegate _socketClosed;

        // Public Delegates
        public delegate void dataReceivedDelegate(ManagedSocket socket, Byte[] data);
        public delegate void sendDataDelegate();
        public delegate void errorDelegate(String message, Exception e);
        public delegate void socketClosedDelegate(ManagedSocket socket);

        // Public Events
        public event dataReceivedDelegate DataReceived
        {
            add { _dataReceived += value; }
            remove { _dataReceived -= value; }
        }
        public event errorDelegate Error
        {
            add { _error += value; }
            remove { _error -= value; }
        }
        public event socketClosedDelegate SocketClosed
        {
            add { _socketClosed += value; }
            remove { _socketClosed -= value; }
        }

        // Public Properties
        public String Key
        {
            get { return _key; }
        }
        public Boolean Active
        {
            get { return _active; }
            internal set { _active = value; }
        }
        public Boolean Reading
        {
            get { return _reading; }
            internal set { _reading = value; }
        }
        public Boolean StopReading
        {
            get { return _stopReading; }
            internal set { _stopReading = value; }
        }
        public IPEndPoint IPEndPoint
        { get { return (IPEndPoint)_socket.RemoteEndPoint; } }

        // Internal Properties
        internal Socket Socket
        {
            get { return _socket; }
        }

        // Public Constructors
        public ManagedSocket(String key, Socket socket)
        {
            _key = key;
            _socket = socket;
            _active = _socket.Connected;

            _sending = new sendDataDelegate(SendData);
            _sendingCallback = new AsyncCallback(endCallback);
            _sending.BeginInvoke(_sendingCallback, null);
        }

        // Public Methods
        public void Open()
        {
            if (!_active)
                return;
        }
        public void Listen()
        {
            if (!_active)
                return;
        }
        public void Close()
        {
            if (!_active)
                return;

            _active = false;
            _socket.Close();
            _socket.Dispose();
            OnSocketClosed();
        }
        public void SendData(Byte[] data)
        {
            if (!_active)
                return;

            _sendSocketEvent.Add(data);
        }
        public void ReadStart()
        {
            if (!_active || _reading)
                return;

            if (Reading)
                return;

            readSocketDelegate readSocket = new readSocketDelegate(managedSocket_readMessages);
            readSocket.BeginInvoke(_socket, new AsyncCallback(managedSocket_endReadMessages), null);
        }
        public void ReadEnd()
        {
            if (!_active || !_reading)
                return;

            StopReading = true;
        }
        public void SendMessage(String message)
        {
            _sendMessages.Add(message);
        }

        // Private Methods
        private void OnDataReceived(Byte[] data)
        {
            if (_dataReceived != null)
                _dataReceived.RaiseEventSafely(new Object[] { this, data });
        }
        private void OnError(String message, Exception e)
        {
            if (_error != null)
                _error.RaiseEventSafely(new Object[] { message, e });
        }
        private void OnSocketClosed()
        {
            if (_socketClosed != null)
                _socketClosed.RaiseEventSafely(new Object[] { this });
        }
        private void managedSocket_readMessages(Socket socket)
        {
            Int32 errorCount = 0;

            while (!StopReading)
            {
                Byte[] bytes = new Byte[1024];
                Int32 received = 0;

                try
                {
                    errorCount += 1;
                    received = socket.Receive(bytes);
                    errorCount -= 1;
                }
                catch (ArgumentNullException e)
                {
                    OnError("", e);
                }
                catch (SocketException e)
                {
                    errorCount -= 1;
                    Close();
                    return;
                }
                catch (SecurityException e)
                {
                    OnError("", e);
                }
                catch (ObjectDisposedException e)
                {
                    OnError("", e);
                }
                catch (Exception e)
                {
                    OnError("", e);
                }

                if (errorCount == 10)
                {
                    OnError("Managed Socket recieved ten errors in a row and has now stopped reading.", null);
                    Close();
                    return;
                }

                Byte[] recievedBytes = new Byte[received];
                Array.Copy(bytes, recievedBytes, received);
                OnDataReceived(recievedBytes);
            }
        }
        private void managedSocket_endReadMessages(IAsyncResult result)
        {
            Reading = false;
            StopReading = false;
        }

        private void SendData()
        {
            List<String> _sendMessages2 = new List<String>();
            List<Byte[]> _sendSocketEvent2 = new List<Byte[]>();
            List<Byte> totalBytes = new List<Byte>();

            lock (_sendMessages)
            {
                _sendMessages2.AddRange(_sendMessages.ToArray());
                _sendMessages.Clear();
            }

            lock (_sendSocketEvent)
            {
                _sendSocketEvent2.AddRange(_sendSocketEvent.ToArray());
                _sendSocketEvent.Clear();
            }

            String message = "";

            foreach (String sendMessage in _sendMessages2)
                message += sendMessage + "\r\n\r\n";

            foreach (Byte[] byteArray in _sendSocketEvent2)
                totalBytes.AddRange(byteArray);

            try
            {
                SocketAsyncEventArgs sockerargs = new SocketAsyncEventArgs();

                if (message != "")
                {
                    Byte[] data = Encoding.ASCII.GetBytes(message);
                    sockerargs.SetBuffer(data, 0, data.Length);
                    _socket.SendAsync(sockerargs);
                }

                if (totalBytes.Count() > 0)
                {
                    sockerargs = new SocketAsyncEventArgs();
                    sockerargs.SetBuffer(totalBytes.ToArray(), 0, totalBytes.Count());
                    _socket.SendAsync(sockerargs);
                }
            }
            catch (Exception e)
            {
                OnError("", e);
            }

            System.Threading.Thread.Sleep(_sleepTimer);
        }
        private void endCallback(IAsyncResult result)
        {
            _sending = new sendDataDelegate(SendData);
            _sendingCallback = new AsyncCallback(endCallback);
            _sending.BeginInvoke(_sendingCallback, null);
        }

    }
    public static class LinqHelpers
    {

        //Private Delegates
        private delegate void RaiseEventSafelyDelegate(Delegate eventDelegate, Object[] args);

        // Static Methods
        public static void RaiseEventSafely(this Delegate eventDelegate, Object[] args)
        {
            if (eventDelegate != null)
                foreach (Delegate singleDelegate in eventDelegate.GetInvocationList())
                {
                    if (typeof(ISynchronizeInvoke).IsAssignableFrom(singleDelegate.Target.GetType()))
                    {
                        ISynchronizeInvoke syncInvoke = (ISynchronizeInvoke)singleDelegate.Target;

                        if ((syncInvoke != null) && (syncInvoke.InvokeRequired))
                        {
                            syncInvoke.Invoke(singleDelegate, args);
                        }
                        else
                        {
                            singleDelegate.DynamicInvoke(args);
                        }
                    }
                    else
                    {
                        singleDelegate.DynamicInvoke(args);
                    }
                }
        }

    }
    public static class RandomFactory
    {
        private static Random globalRandom = new Random();

        public static Random Create()
        {
            lock (globalRandom)
            {
                Random newRandom = new Random(globalRandom.Next());
                return newRandom;
            }
        }
    }
}
